/*
* Copyright (c) 2020 The canvas Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/canvas.git
* Email:          505544956@qq.com
* FileName:       app.hpp
* Create Time:    2020/02/21 21:20:48
* Description:    /
*/

#ifndef __APP_H_
#define __APP_H_

#include <SDL.h>
#include <SDL_syswm.h>
#include <SDL_mixer.h>
#include <canvas_2d.h>

#include <random>
#include <ctime>
#include <string>
#include <vector>
#include <functional>

#define APP_RUN_FASTEST ((Uint32)-1)
#define APP_RUN_SLOWEST ((Uint32)0)
#define APP_USER_EVENT_ID 100

struct _sTimerData {
  void *app;
  SDL_TimerID id;
  Uint32 interval;
  void *param;
};
static Uint32 SDLCALL _sdl_timer_callback(Uint32 interval, void *param);

class AppPaintInterface {
  public:
    virtual void draw(cvs::c2d::ITarget *target) {}
    virtual void timer(SDL_TimerID id, Uint32 interval, void *param) {}
    virtual void handle_event(const SDL_Event &evt) {}
};

class App {
    friend Uint32 SDLCALL _sdl_timer_callback(Uint32 interval, void *param);
  public:
    App() : factory_(0), render_(0), target_(0), window_(0)
        , fps_(0) {}
    virtual ~App() {
      uninit();
    }
  public:
    virtual cvs::Result init(cvs::c2d::RenderType type = cvs::c2d::RenderType::Default) {
      if (SDL_Init(SDL_INIT_EVENTS | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0)
        return CVS_E_Fail;
      auto rst = init_sound();
      if (cvs::Succeeded(rst))
        rst = cvs::create_factory(&factory_);
      if (cvs::Succeeded(rst))
        load_renders();
      if (cvs::Succeeded(rst))
        rst = factory_->create_2d_render(&render_, type);
      if (cvs::Succeeded(rst))
        rst = internal_create_resource();
      if (cvs::Failed(rst))
        uninit();
      return rst;
    }
    virtual cvs::Result create_window(const char *title, cvs::Size size, Uint32 flags) {
      if (window_) return CVS_S_Ok;

      window_ = SDL_CreateWindow(title
          , SDL_WINDOWPOS_CENTERED
          , SDL_WINDOWPOS_CENTERED
          , size.w, size.h
          , flags & (~SDL_WINDOW_SHOWN) | SDL_WINDOW_HIDDEN);
      if (!window_) return CVS_E_Fail;

      void *handle = 0;
      cvs::Result rst = window_handle(&handle);

      if (cvs::Succeeded(rst))
        rst = render_->create_window_target(handle, size, &target_);
      
      if (cvs::Succeeded(rst))
        rst = internal_create_target_resource();

      if (cvs::Succeeded(rst)) {
        if (flags & SDL_WINDOW_SHOWN)
          SDL_ShowWindow(window_);
      }
      
      if (cvs::Failed(rst)) {
        SDL_DestroyWindow(window_);
        window_ = 0;
        internal_release_target_resource();
        cvs::SaveDecref(target_); target_ = 0;
      }
      
      return rst;
    }
    virtual void run(Uint32 fps) {
      Uint32 time_last = 0;
      Uint32 time_cur = 0;

      SDL_Event evt;
      if (fps == APP_RUN_SLOWEST) {
        fps_ = 0.f;
        while (true) {
          SDL_WaitEvent(&evt);
          if (!internal_handle_event(evt))
            break;
        }
      } else if (fps == APP_RUN_FASTEST) {
        while (true) {
          SDL_PollEvent(&evt);
          if (!internal_handle_event(evt))
            break;
          time_cur = SDL_GetTicks();
          cal_fps(time_last, time_cur);
          time_last = time_cur;
          on_internal_draw();
        }
      } else {
        Uint32 tt = 1000.f/fps;
        while (true) {
          SDL_PollEvent(&evt);
          if (!internal_handle_event(evt))
            break;
          time_cur = SDL_GetTicks();
          cal_fps(time_last, time_cur);
          time_last = time_cur;
          on_internal_draw();
          if (time_cur-time_last < fps)
            SDL_Delay(tt + time_last - time_cur);
        }
      }
    }
    virtual void uninit() {
      if (window_) {
        SDL_DestroyWindow(window_);
        window_ = 0;
      }
      internal_release_target_resource();
      cvs::SaveDecref(target_); target_ = 0;
      internal_release_resource();
      cvs::SaveDecref(render_); render_ = 0;
      if (factory_) factory_->unload_all_render();
      cvs::SaveDecref(factory_); factory_ = 0;
      uninit_sound();
      SDL_Quit();
    }
    virtual cvs::dtype fps() const { return fps_; }
    virtual cvs::Size window_size() const {
      int w, h;
      SDL_GetWindowSize(window_, &w, &h);
      return cvs::Size(w, h);
    }
    virtual cvs::Rect window_rect() const {
      auto size = window_size();
      return cvs::Rect(0.f, 0.f, size.w, size.h);
    }
    virtual void invaldate() const {
      if (!window_) return;
      SDL_Event evt;
      evt.window.type = SDL_WINDOWEVENT;
      evt.window.event = SDL_WINDOWEVENT_EXPOSED;
      evt.window.windowID = SDL_GetWindowID(window_);
      SDL_PushEvent(&evt);
    }
    virtual void do_user_event(Sint32 id, void *data1 = 0, void *data2 = 0) {
      if (!window_) return;
      SDL_Event evt;
      evt.user.type = SDL_USEREVENT;
      evt.user.windowID = SDL_GetWindowID(window_);
      evt.user.code = id;
      evt.user.data1 = data1;
      evt.user.data2 = data2;
      SDL_PushEvent(&evt);
    }
    virtual SDL_TimerID add_timer(Uint32 interval, void *param = 0) {
      auto p = new _sTimerData;
      p->app = this;
      p->param = param;
      p->interval = interval;
      p->id = SDL_AddTimer(interval, _sdl_timer_callback, p);
      if (p->id == 0) delete p;
      timers_.push_back(p);
      return p->id;
    }
    virtual bool remove_timer(SDL_TimerID id) {
      bool r = SDL_RemoveTimer(id) == SDL_TRUE;
      if (r) {
        for (auto it = timers_.begin(); it != timers_.end(); ++it) {
          if ((*it)->id == id) {
            delete (*it);
            timers_.erase(it);
            break;
          }
        }
      }
      return r;
    }
    virtual Mix_Music* load_music(const char *file) {
      return Mix_LoadMUS(file);
    }
    virtual void unload_music(Mix_Music *music) {
      Mix_FreeMusic(music);
    }
    virtual bool play_sound(Mix_Music *music, int loops = 0) {
      Mix_PlayMusic(music, loops);
      return true;
    }
  public:
    void add_painter(AppPaintInterface *p) { if (p) painters_.push_back(p); }
    void remove_painter(AppPaintInterface *p) {
      if (!p) return;
      for (auto it = painters_.begin(); it != painters_.end(); ++it) {
        if (*it == p) {
          painters_.erase(it);
          break;
        }
      }
    }
    void clear_painter() { painters_.clear(); }
  protected:
    cvs::Result window_handle(void **handle) {
      SDL_SysWMinfo info;
      SDL_VERSION(&info.version);
      if (window_ && SDL_GetWindowWMInfo(window_, &info)) {
        #if defined(SDL_VIDEO_DRIVER_WINDOWS)
          *handle = info.info.win.window;
        #elif defined(SDL_VIDEO_DRIVER_WINRT)
          *handle = info.info.winrt.window;
        #elif defined(SDL_VIDEO_DRIVER_X11)
          *handle = info.info.x11.window;
        #elif defined(SDL_VIDEO_DRIVER_DIRECTFB)
          *handle = info.info.dfb.window;
        #elif defined(SDL_VIDEO_DRIVER_COCOA)
          *handle = info.info.cocoa.window;
        #elif defined(SDL_VIDEO_DRIVER_UIKIT)
          *handle = info.info.uikit.window;
        #elif defined(SDL_VIDEO_DRIVER_WAYLAND)
          *handle = info.info.wl.window;
        #elif defined(SDL_VIDEO_DRIVER_MIR)
          *handle = info.info.mir.connection;
        #elif defined(SDL_VIDEO_DRIVER_ANDROID)
          *handle = info.info.android.window;
        #elif defined(SDL_VIDEO_DRIVER_VIVANTE)
          *handle = info.info.vivante.window;
        #endif
        return CVS_S_Ok;
      }
      return CVS_E_Fail;
    }
    void cal_fps(Uint32 last_time, Uint32 cur_time) {
      static Uint32 frameCount = 0;
      static Uint32 elapsed_time = 0;

      ++frameCount;
      elapsed_time += cur_time - last_time;

      if (elapsed_time > 1000) {
        fps_ = (cvs::dtype)frameCount / (cvs::dtype)elapsed_time * 1000.f;
        frameCount = 0;
        elapsed_time = 0;
      }
    }
    cvs::Result init_sound() {
      Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
      return CVS_S_Ok;
    }
    void uninit_sound() {
      Mix_CloseAudio();
    }
    unsigned random(unsigned min, unsigned max) {
      static std::default_random_engine engine(time(0));
      std::uniform_int_distribution<unsigned> u(min, max);
      return u(engine);
    }
  protected:
    virtual void load_renders() {
      if (!factory_) return;
      factory_->load_render("canvas_d2d");
      factory_->load_render("canvas_gdip");
      factory_->load_render("canvas_sdl");
    }
    virtual cvs::Result draw_fps(const cvs::c2d::TextAlignHor halign = cvs::c2d::TextAlignHor::Right
          , const cvs::c2d::TextAlignVer valign = cvs::c2d::TextAlignVer::Top
          , const cvs::Color &color = cvs::Color()) {
      if (!target_) return CVS_E_Fail;

      std::wstring text;
      cvs::c2d::FontFormat font;
      cvs::c2d::TextFormat format;

      text = L"FPS: ";
      text += std::to_wstring((Uint32)fps());
      font.family = L"";
      font.size = 20.f;
      font.bold = false;
      font.italic = false;
      font.underline = false;
      font.strikethrough = false;
      format.halign = halign;
      format.valign = valign;
      format.direction = cvs::c2d::TextDirection::LeftToRight;
      format.trimming = cvs::c2d::TextTrimming::None;
      format.word_wrap = false;
      format.auto_width = false;
      format.auto_height = false;

      auto win_size = window_size();

      cvs::c2d::IShapeText *fps_shape = 0;
      cvs::c2d::ISolidColorBrush *fps_brush = 0;

      auto rst = render_->create_shape_text(text.c_str(), text.size()
          , cvs::Rect(0.f, 0.f, win_size.w, win_size.h)
          , font, format, &fps_shape);

      if (cvs::Succeeded(rst))
        rst = target_->create_solid_color_brush(color, &fps_brush);

      if (cvs::Succeeded(rst))
        rst = target_->fill_shape(fps_shape, fps_brush);

      cvs::SaveDecref(fps_shape);
      cvs::SaveDecref(fps_brush);

      return rst;
    }
    virtual cvs::Result draw_text(const std::wstring text, const cvs::Rect &rc
          , const cvs::Color &color
          , const cvs::c2d::FontFormat font, const cvs::c2d::TextFormat format) {
      if (!target_) return CVS_E_Fail;

      cvs::c2d::IShapeText *shape = 0;
      cvs::c2d::ISolidColorBrush *brush = 0;

      auto rst = render_->create_shape_text(text.c_str(), text.size()
          , rc, font, format, &shape);

      if (cvs::Succeeded(rst))
        rst = target_->create_solid_color_brush(color, &brush);

      if (cvs::Succeeded(rst))
        rst = target_->fill_shape(shape, brush);

      cvs::SaveDecref(shape);
      cvs::SaveDecref(brush);

      return rst;
    }
    virtual cvs::Result draw_text(const std::wstring text, const cvs::Rect &rc
          , cvs::dtype size
          , const cvs::Color &color = cvs::Color()
          , const cvs::c2d::TextAlignHor halign = cvs::c2d::TextAlignHor::Center
          , const cvs::c2d::TextAlignVer valign = cvs::c2d::TextAlignVer::Middle) {
      if (!target_) return CVS_E_Fail;

      cvs::c2d::FontFormat font;
      cvs::c2d::TextFormat format;

      font.family = L"";
      font.size = size;
      font.bold = false;
      font.italic = false;
      font.underline = false;
      font.strikethrough = false;
      format.halign = halign;
      format.valign = valign;
      format.direction = cvs::c2d::TextDirection::LeftToRight;
      format.trimming = cvs::c2d::TextTrimming::None;
      format.word_wrap = false;
      format.auto_width = false;
      format.auto_height = false;

      return draw_text(text, rc, color, font, format);
    }
    virtual cvs::Result create_bitmap_from_file(const char *file, cvs::c2d::IBitmap **bitmap) {
      cvs::c2d::ISurface *surface = 0;
      cvs::c2d::IBitmap *tmp_bitmap = 0;
      auto rst = render_->create_surface(&surface);
      if (cvs::Succeeded(rst))
        rst = surface->load_file(file);
      if (cvs::Succeeded(rst))
        rst = target_->create_bitmap(&tmp_bitmap);
      if (cvs::Succeeded(rst))
        rst = tmp_bitmap->create(surface);
      if (cvs::Succeeded(rst)) {
        *bitmap = tmp_bitmap;
        tmp_bitmap->incref();
      }
      cvs::SaveDecref(surface);
      cvs::SaveDecref(tmp_bitmap);
      return rst;
    }
    virtual cvs::Result create_bitmap_from_buffer(const char *buffer, cvs::uint len, cvs::c2d::IBitmap **bitmap) {
      cvs::c2d::ISurface *surface = 0;
      cvs::c2d::IBitmap *tmp_bitmap = 0;
      auto rst = render_->create_surface(&surface);
      if (cvs::Succeeded(rst))
        rst = surface->load_buffer(buffer, len);
      if (cvs::Succeeded(rst))
        rst = target_->create_bitmap(&tmp_bitmap);
      if (cvs::Succeeded(rst))
        rst = tmp_bitmap->create(surface);
      if (cvs::Succeeded(rst)) {
        *bitmap = tmp_bitmap;
        tmp_bitmap->incref();
      }
      cvs::SaveDecref(surface);
      cvs::SaveDecref(tmp_bitmap);
      return rst;
    }
  protected:
    virtual cvs::Result internal_create_resource() { return create_resource(); }
    virtual void internal_release_resource() { release_resource(); }
    virtual cvs::Result internal_create_target_resource() { return create_target_resource(); }
    virtual void internal_release_target_resource() { release_target_resource(); }

    virtual bool internal_handle_event(const SDL_Event &evt) {
      if (!handle_event(evt)) return false;

      switch (evt.type) {
        case SDL_QUIT: return false;
        case SDL_KEYDOWN:
        case SDL_KEYUP:
          on_keyboard(evt.key);
          break;
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONUP:
          on_mouse_click(evt.button);
          break;
        case SDL_MOUSEMOTION:
          on_mouse_move(evt.motion);
          break;
        case SDL_WINDOWEVENT:
          on_internal_window(evt.window);
          break;
        case SDL_USEREVENT:
          on_internal_user(evt.user.code, evt.user.data1, evt.user.data2);
          break;
      }

      for (auto p : painters_) {
        if (p) p->handle_event(evt);
      }

      return true;
    }

    virtual void on_internal_window(const SDL_WindowEvent &evt) {
      switch (evt.event) {
        case SDL_WINDOWEVENT_SHOWN:
          on_shown();
          break;
        case SDL_WINDOWEVENT_HIDDEN:
          on_hidden();
          break;
        case SDL_WINDOWEVENT_EXPOSED:
          on_internal_draw();
          break;
        case SDL_WINDOWEVENT_MOVED:
          on_moved(evt.data1, evt.data2);
          break;
        case SDL_WINDOWEVENT_RESIZED:
          target_->resize(cvs::Size(evt.data1, evt.data2));
          on_resize(evt.data1, evt.data2);
          break;
        case SDL_WINDOWEVENT_MINIMIZED:
          on_minimized();
          break;
        case SDL_WINDOWEVENT_MAXIMIZED:
          on_maximized();
          break;
        case SDL_WINDOWEVENT_RESTORED:
          on_restored();
          break;
        case SDL_WINDOWEVENT_ENTER:
          on_mouse_enter();
          break;
        case SDL_WINDOWEVENT_LEAVE:
          on_mouse_leave();
          break;
        case SDL_WINDOWEVENT_FOCUS_GAINED:
          on_setfocus();
          break;
        case SDL_WINDOWEVENT_FOCUS_LOST:
          on_killfocus();
          break;
        case SDL_WINDOWEVENT_CLOSE:
          on_internal_close();
          break;
      }
    }
    virtual void on_internal_user(Sint32 id, void *data1, void *data2) {
      if (id == 1) {
        auto t = (_sTimerData*)data1;
        if (t) {
          on_timer(t->id, t->interval, t->param);
          for (auto p : painters_) {
            if (p) p->timer(t->id, t->interval, t->param);
          }
        }
      } else {
        on_user(id, data1, data2);
      }
    }
    virtual void on_internal_close() {
      for (auto p : timers_) {
        SDL_RemoveTimer(p->id);
        delete p;
      }
      timers_.clear();
      on_close();
    }
    virtual void on_internal_draw() {
      on_begin_draw();
      on_draw();
      for (auto p : painters_)
        if (p) p->draw(target_);
      on_end_draw();
    }
  protected:
    virtual cvs::Result create_resource() { return CVS_S_Ok; }
    virtual void release_resource() {}
    virtual cvs::Result create_target_resource() { return CVS_S_Ok; }
    virtual void release_target_resource() {}

    virtual bool handle_event(const SDL_Event &evt) { return true; }
    virtual void on_mouse_move(const SDL_MouseMotionEvent &evt) {}
    virtual void on_mouse_click(const SDL_MouseButtonEvent &evt) {}
    virtual void on_keyboard(const SDL_KeyboardEvent &evt) {}
    virtual void on_window(const SDL_WindowEvent &evt) {}
    virtual void on_mouse_enter() {}
    virtual void on_mouse_leave() {}
    virtual void on_setfocus() {}
    virtual void on_killfocus() {}
    virtual void on_shown() {}
    virtual void on_hidden() {}
    virtual void on_begin_draw() { target_->begin_draw(); }
    virtual void on_draw() {}
    virtual void on_end_draw() { target_->end_draw(); }
    virtual void on_moved(int x, int y) {}
    virtual void on_resize(int w, int h) {}
    virtual void on_minimized() {}
    virtual void on_maximized() {}
    virtual void on_restored() {}
    virtual void on_close() {}
    virtual void on_timer(SDL_TimerID id, Uint32 interval, void *param) {}
    virtual void on_user(Sint32 id, void *data1, void *data2) {}
  protected:
    cvs::IFactory *factory_;
    cvs::c2d::IRender *render_;
    cvs::c2d::IWindowTarget *target_;
    SDL_Window *window_;
    cvs::dtype fps_;
    std::vector<_sTimerData*> timers_;
    std::vector<AppPaintInterface*> painters_;
};

Uint32 SDLCALL _sdl_timer_callback(Uint32 interval, void *param) {
  auto p = (_sTimerData*)param;
  if (!p) return 0;
  p->interval = interval;
  auto app = (App*)p->app;
  if (app) {
    app->do_user_event(1, param);
  }
  return interval;
}

struct AppCtrlData {
  cvs::c2d::IBitmap *bitmap;
  cvs::Rect bitmap_src;
  cvs::Color back_color;
  cvs::Color border_color;
  cvs::Color text_color;
  cvs::c2d::TextFormat text_format;
  cvs::c2d::FontFormat font_format;
  AppCtrlData() {
    bitmap = 0;
    memset(&text_format, 0, sizeof(text_format));
    memset(&font_format, 0, sizeof(font_format));
  }
};

enum class AppCtrlState : cvs::uint8 {
  Normal, Hot, Pushed, Disabled
};

// ctrl
class AppControl : public AppPaintInterface {
  public:
    AppCtrlData normal, hot, pushed, disabled;
  public:
    AppControl(App *app) : app_(app) {
      state_ = AppCtrlState::Normal;
      border_size_ = 0.f;
      back_color_used_ = false;
      shape_rect_ = 0;
      shape_text_ = 0;
      brush_ = 0;
      visible_ = true;
      enabled_ = true;
      if (app_) app_->add_painter(this);
    }
    virtual ~AppControl() {
      if (app_) app_->add_painter(this);
      free_shape();
    }
  public:
    virtual void move(cvs::dtype dx, cvs::dtype dy) { rect_.x += dx; rect_.y += dy; }
    virtual void move_to(cvs::dtype x, cvs::dtype y) { rect_.x = x; rect_.y = y; }
    virtual void set_rect(const cvs::Rect &rc) { rect_ = rc; }
    virtual cvs::Rect rect() const { return rect_; }
    virtual void set_border_size(cvs::dtype size) { border_size_ = size; }
    virtual cvs::dtype border_size() const { return border_size_; }
    virtual void set_radius(const cvs::Size &radius) { radius_ = radius; }
    virtual cvs::Size radius() const { return radius_; }
    virtual void set_use_back_color(bool use) { back_color_used_ = use; }
    virtual bool use_back_color() const { return back_color_used_; }
    virtual void set_text(const std::wstring text) { text_ = text; }
    virtual std::wstring text() const { return text_; }
    virtual void set_callback(std::function<void(AppControl*)> fun) { callback_ = fun; }
    virtual std::function<void(AppControl*)> callback() const { return callback_; }
    virtual void invaldate() { if (app_) app_->invaldate(); }
    virtual void set_visible(bool v) { visible_ = v; }
    virtual bool visible() const { return visible_; }
    virtual void set_enable(bool e) { enabled_ = e; }
    virtual bool enabled() const { return enabled_; }
  protected:
    virtual void draw(cvs::c2d::ITarget *target) override {
      if (!visible()) return;

      if (!target) return;
      auto d = get_paint_data();
      if (!d) return;
      gen_shape(target, d);

      // back color
      if (back_color_used_ && shape_rect_ && brush_) {
        brush_->set_color(d->back_color);
        target->fill_shape(shape_rect_, brush_);
      }

      // back image
      if (d->bitmap) {
        target->draw_bitmap(d->bitmap, &rect_, &d->bitmap_src);
      }

      // text
      if (shape_text_ && brush_) {
        brush_->set_color(d->text_color);
        target->fill_shape(shape_text_, brush_);
      }

      // border
      if (brush_ && shape_rect_ && border_size_ > 0.f) {
        brush_->set_color(d->border_color);
        target->draw_shape(shape_rect_, brush_, border_size_);
      }
    }
    virtual void handle_event(const SDL_Event &evt) override {
      if (!visible()) return;
      auto last_state = state_;
      if (!enabled()) {
        state_ = AppCtrlState::Disabled;
      } else {
        switch (evt.type) {
          case SDL_MOUSEBUTTONDOWN:
            if (evt.button.button == SDL_BUTTON_LEFT) {
              if (pt_in_rect(evt.button.x, evt.button.y)) {
                state_ = AppCtrlState::Pushed;
                if (callback_) callback_(this);
              }
            }
            break;
          case SDL_MOUSEBUTTONUP:
            if (evt.button.button == SDL_BUTTON_LEFT) {
              if (pt_in_rect(evt.motion.x, evt.motion.y)) {
                state_ = AppCtrlState::Hot;
              } else {
                state_ = AppCtrlState::Normal;
              }
            }
            break;
          case SDL_MOUSEMOTION:
            if (pt_in_rect(evt.motion.x, evt.motion.y)) {
              state_ = AppCtrlState::Hot;
            } else {
              state_ = AppCtrlState::Normal;
            }
            break;
        }
      }
      if (last_state != state_)
        invaldate();
    }
  protected:
    virtual bool pt_in_rect(cvs::dtype x, cvs::dtype y) const {
      if (x < rect_.l() || x > rect_.r()) return false;
      if (y < rect_.t() || y > rect_.b()) return false;
      return true;
    }
    virtual AppCtrlData* get_paint_data() {
      switch (state_) {
        case AppCtrlState::Normal: return &normal;
        case AppCtrlState::Hot: return &hot;
        case AppCtrlState::Pushed: return &pushed;
        case AppCtrlState::Disabled: return &disabled;
      }
      return 0;
    }
    virtual void gen_shape(cvs::c2d::ITarget *target, const AppCtrlData *acd) {
      if (!target || !acd) return;
      auto render = target->render();
      if (!render) return;

      if (!shape_rect_) {
        render->create_shape_rect(rect_, radius_, &shape_rect_);
      } else {
        shape_rect_->set(rect_, radius_);
      }

      if (!shape_text_) {
        render->create_shape_text(text_.c_str(), text_.size()
            , rect_, acd->font_format, acd->text_format, &shape_text_);
      } else {
        shape_text_->set(text_.c_str(), text_.size()
            , rect_, acd->font_format, acd->text_format);
      }

      if (!brush_)
        target->create_solid_color_brush(cvs::Color(), &brush_);
    }
    virtual void free_shape() {
      cvs::SaveDecref(shape_rect_); shape_rect_ = 0;
      cvs::SaveDecref(shape_text_); shape_text_ = 0;
      cvs::SaveDecref(brush_); brush_ = 0;
    }
  protected:
    App *app_;
    AppCtrlState state_;
    cvs::Rect rect_;
    cvs::dtype border_size_;
    cvs::Size radius_;
    bool back_color_used_;
    std::wstring text_;
    std::function<void(AppControl*)> callback_;

    cvs::c2d::IShapeRect *shape_rect_;
    cvs::c2d::IShapeText *shape_text_;
    cvs::c2d::ISolidColorBrush *brush_;

    bool visible_;
    bool enabled_;
};

// sprite
class AppSprite : public AppPaintInterface {
  public:
    AppSprite(App *app) : app_(app) { if (app_) app_->add_painter(this); }
    virtual ~AppSprite() { if (app_) app_->remove_painter(this); }
  public:
    virtual void move(cvs::dtype dx, cvs::dtype dy) { pos_.x += dx; pos_.y += dy; }
    virtual void move_to(cvs::dtype x, cvs::dtype y) { pos_.x = x; pos_.y = y; }
    virtual void set_pos(cvs::dtype x, cvs::dtype y) { pos_ = cvs::Point(x,y); }
    virtual cvs::Point pos() const { return pos_; }
    virtual void invaldate() { if (app_) app_->invaldate(); }
  protected:
    virtual bool add_timer(Uint32 interval, void *param = 0) {
      if (!app_) return false;
      timer_id_ = app_->add_timer(interval, param);
      return timer_id_ != 0;
    }
    virtual bool remove_timer() {
      if (!app_) return false;
      return app_->remove_timer(timer_id_);
    }
  protected:
    virtual void timer(SDL_TimerID id, Uint32 interval, void *param) {}
    virtual void draw(cvs::c2d::ITarget *target) override {}
  protected:
    App *app_;
    cvs::Point pos_;
    SDL_TimerID timer_id_;
};

#endif // __APP_H_